//进程池是一个超市，一方面，从任务池中拿任务，另一方面，将任务派发到各个信道中
#include "Task.hpp"
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>


//先描述
class Channel
{
public:
    Channel(int wfd,pid_t subid)
        :_wfd(wfd)
        ,_subid(subid)
    {}
    void Send(int taskcode)
    {
        //信道向子进程（读端）发送一个任务码
        write(_wfd,&taskcode,sizeof(taskcode));
        std::cout << "向子进程 pid:" << _subid << "发送任务" << std::endl;
    }
    void Close()
    {
        close(_wfd);
    }
    void Wait()
    {
        //能执行stop的就是父进程
        waitpid(_subid,nullptr,0);
    }

    ~Channel(){}
private:
    //怎么描述一个信道
    //对应的文件描述符是什么
    int _wfd;
    //子进程id是什么
    pid_t _subid;
    
};

//再组织
class ChannelManager
{
public:
    ChannelManager()
        :_next(0)
    {}
    //提供插入新信道的方法
    void Insert(int wfd,pid_t subid)
    {
        //emplace_back不能传递一个对象,而是要传递参数
        _channels.emplace_back(wfd,subid);
    }
    //提供选择信道的方法
    Channel& Select()
    {
        Channel &c = _channels[_next];
        _next++;
        _next %= _channels.size();
        return c;
    }
    //提供关闭写端的方法
    void StopSubProcess()
    {
        for(auto &ch : _channels)
        {
            ch.Close();
        }
    }
    //提供等待子进程的方法
    void WaitSubProcess()
    {
        for(auto& ch: _channels)
        {
            ch.Wait();
        }
    }
    ~ChannelManager(){}
private:
    //组织多个信道需要什么
    std::vector<Channel> _channels;
    int _next;
};




class ProcessPool
{
public:
    //构造函数，确定要创建的进程数量
    ProcessPool(int num = 5)
        :_process_num(num)
    {
        std::cout << "创建进程池对象 ..." << std::endl;
        _tm.Register(PrintLog);
        _tm.Register(DownLoad);
        _tm.Register(UpLoad);
        std::cout << "注册方法成功" << std::endl;
    }
    //子进程调用work方法持续从属于自己的管道文件中读取任务码，执行，然后再次读取
    void Work(int rfd)
    {
        //子进程一旦调用work相当于读端不需要进行管理，一旦有任务，它自己就会响应
        while(true)
        {
            int code = -1;
            int n = read(rfd,&code,sizeof(code));
            if(n > 0)
            {
                //判断是否是任务
                if(n != sizeof(code))
                {
                    continue;
                }
                //收到任务码，开始执行
                std::cout << "子进程收到任务码" << code << "开始执行 ..."<< std::endl;
                _tm.Execute(code);
            }
            else if(n == 0)
            {
                //写端关闭了
                break;
            }
            else
            {
                //读取异常
                break;
            }
        }
    }
    void Run()
    {
        //Run是真正管理和连接任务池和子进程的核心内容
        //首先从任务池中挑选一个任务码
        int taskcode = _tm.Code();
        //然后选择一个信道发送这个任务给对应子进程
        //均衡负载
        Channel & c = _cm.Select();

        c.Send(taskcode);
        
    }
    //启动进程池
    bool Start()
    {   //循环创建进程
        for(int i = 0; i < _process_num;i++)
        {
            //进程池和进程之间通过管道来通信，进程池就是一个父进程
            int pipefd[2];
            int n = pipe(pipefd);
            //创建管道文件失败
            if(n < 0)
            {
                return false;
            }
            //创建子进程
            pid_t subid = fork();
            if(subid < 0)
            {
                return false;
            }
            else if(subid == 0)
            {
                //child
                //父子进程之间进行通信
                //父进程写子进程读
                std::cout << "创建子进程 " << i << "..." << std::endl;
                close(pipefd[1]);
                Work(pipefd[0]);
                close(pipefd[0]);
                exit(0);
            }
            else
            {
                close(pipefd[0]);
                //信道管理者管理写端，子进程创建好，管道创建好之后，就向信道中插入管道文件描述符和对应的子进程id
                _cm.Insert(pipefd[1],subid);

            }
        }
        return true;
    }
    //提供关闭进程池的方法
    void Stop()
    {
        //work一直在循环等待新任务从信道写端被写入,Run里面也一直在从任务池里拿任务
        //此时通过信道管理者关闭所有写端（信道拥有所有写端信息），等待所有子进程（信道拥有所有的子进程id)
        _cm.StopSubProcess();
        _cm.WaitSubProcess();
    }
    ~ProcessPool(){}
private:
    int _process_num;
    TaskManager _tm;
    ChannelManager _cm;
};